import { ElementRef } from "@angular/core";
import { Object3D, PerspectiveCamera, Scene, Vector3 } from "three";
import { ThreeRenderer } from "../three/renderer";
import { ThreeScene } from "../three/scene";
import { Perspective } from "../camera/perspective";


import { Tween, Easing, Group } from "@tweenjs/tween.js";
import { CSS3DObject, CSS3DRenderer } from "three/examples/jsm/renderers/CSS3DRenderer.js";
import { TrackballControls } from "three/examples/jsm/controls/TrackballControls.js";

interface CssObj {
    [key: string]: Object3D[]
}


export class SignRenderer {

    private camera: PerspectiveCamera = new PerspectiveCamera;
    private scene: Scene;
    private renderer: CSS3DRenderer;
    private controls!: TrackballControls;
    private group = new Group();

    private perspective = new Perspective();
    private threeRenderer = new ThreeRenderer();
    private threeScene = new ThreeScene();
    private elementRef!: ElementRef;

    private num = 0;

    private obj: CssObj = {
        chaos: [],
        table: [],
        sphere: [],
        helix: [],
        grid: []
    }

    private preGuessTotal = 200;
    private vector3 = new Vector3();
    private list = <any>[];
    private userList = <any>[];
    private imgList: HTMLImageElement[] = [];

    private timer = 0;
    private initIndex = 0;

    private tabs = ["table", "sphere", "helix", "grid", "chaos"];

    constructor() {
        this.scene = this.threeScene.create();
        this.renderer = this.threeRenderer.CSS3D()
    }

    init(elementRef: ElementRef) {
        this.elementRef = elementRef;
        this.camera = this.perspective.init(40, this.elementRef.nativeElement.offsetWidth / this.elementRef.nativeElement.offsetHeight, 1, 10000)

        this.renderer.setSize(this.elementRef.nativeElement.offsetWidth, this.elementRef.nativeElement.offsetHeight);

        this.controls = new TrackballControls(this.camera, this.elementRef.nativeElement);
        this.controls.noZoom = true;
        this.controls.noPan = true;
        this.controls.minDistance = 600;
        this.controls.maxDistance = 6000;
        this.controls.addEventListener('change', () => {
            this.render();
        });

        return this.renderer;
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));
        this.group.update();
        this.updateByMouse();
        this.controls.update();
    }

    render() {
        this.renderer.render(this.scene, this.camera);
    }

    onWindowResize() {
        this.camera.aspect = this.elementRef.nativeElement.offsetWidth / this.elementRef.nativeElement.offsetHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(this.elementRef.nativeElement.offsetWidth, this.elementRef.nativeElement.offsetHeight);

        this.render();
    }

    //捕捉鼠标
    updateByMouse() {
        this.num += Math.PI / 1000 * 2;
        this.camera.position.x = Math.cos(this.num) * 2120 * 1.3;
        this.camera.position.z = Math.sin(this.num) * 2120 * 1.3;
        this.camera.position.y = 0;
        this.camera.up.x = 0;
        this.camera.up.y = 1;
        this.camera.up.z = 0;
    }

    initTableData(e: number) {
        const t = new Object3D();
        const a = Math.floor(e / 20);
        const n = e % 20;
        t.position.x = n * 106 - 1025;
        t.position.y = -(a * 106) + this.preGuessTotal * 2.5;
        this.obj['table'].push(t);
    }

    initSphereData(e: number) {
        const n = Math.acos(1 - 2 * e / this.preGuessTotal);
        const s = Math.sqrt(this.preGuessTotal * Math.PI) * n;
        const a = new Object3D();
        a.position.setFromSphericalCoords(600, n, s);
        this.vector3.copy(a.position).multiplyScalar(3);
        a.lookAt(this.vector3);
        this.obj['sphere'].push(a);
    }

    initGridData(e: number) {
        const t = new Object3D();
        t.position.x = e % 5 * 200 - 200;
        t.position.y = -(Math.floor(e / 5) % 5) * 200 + 400;
        t.position.z = Math.floor(e / 25) * this.preGuessTotal - 600;
        this.obj['grid'].push(t);
    }

    initHelixData(e: number) {
        const t = e * .15 + Math.PI;
        const a = -(e * 1600 / this.preGuessTotal) + 800;
        const n = new Object3D();
        n.position.setFromCylindricalCoords(500, t, a);
        this.vector3.x = n.position.x * 2;
        this.vector3.y = n.position.y * 2;
        this.vector3.z = n.position.z * 2;
        n.lookAt(this.vector3);
        this.obj['helix'].push(n);
    }

    initChaosData(e: number) {
        for (var i = 0, t = this.list.length; i < t; i++) {
            const n = new Object3D();
            let s = Math.random() * 3400 - 1700;
            s < 400 && s > -400 && (s < 0 ? s -= 400 : s += 400);
            n.position.x = Math.random() * 3400 - 1700;
            n.position.y = s;
            n.position.z = Math.random() * 3400 - 1700;
            n.lookAt(this.vector3);
            this.obj['chaos'].push(n);
        }
    }

    initUserList() {
        for (let t = 0; t < this.preGuessTotal; t++) {
            this.initData(this.userList[t], t, false)
        }
    }

    autoTransform() {

        this.setAnimal(this.obj[this.tabs[this.initIndex]], 1000);
        clearInterval(this.timer);

        this.timer = window.setInterval(() => {
            this.initIndex = this.initIndex + 1 >= this.tabs.length - 1 ? 0 : this.initIndex + 1;
            const e = this.tabs[this.initIndex];
            this.setAnimal(this.obj[e], 1000)
        }, 6000)
    }

    clear() {
        this.scene.clear();

        if (this.timer) {
            clearInterval(this.timer);
        }
    }

    initData(e: any, t: number, a = true) {
        const { avatar } = e || {};
        if (a) {
            const o = this.userList.length > this.preGuessTotal ? Math.floor(Math.random() * this.preGuessTotal) : t;
            setTimeout(() => {
                this.imgList[o].src = avatar || ''
            }, 2000);
            return
        }
        const div = document.createElement("div");
        div.className = "element";
        const img = document.createElement("img");
        img.className = "symbol";
        avatar && (img.src = avatar);
        this.imgList.push(img);
        div.appendChild(img);
        const u = new CSS3DObject(div);
        u.position.x = Math.random() * 3400 - 1700;
        u.position.y = Math.random() * 3400 - 1700;
        u.position.z = Math.random() * 3400 - 1700;

        this.scene.add(u);

        this.list.push(u);

        if (this.tabs.includes("table")) {
            this.initTableData(t);
        }
        if (this.tabs.includes("sphere")) {
            this.initSphereData(t);
        }
        if (this.tabs.includes("helix")) {
            this.initHelixData(t);
        }
        if (this.tabs.includes("grid")) {
            this.initGridData(t);
        }
        if (this.tabs.includes("chao")) {
            this.initChaosData(t);
        }

    }

    setAnimal(obj: Object3D[], t: number) {
        this.group.removeAll();
        for (let i = 0; i < this.list.length; i++) {
            const u = this.list[i];
            const o = obj[i];

            let tw = new Tween(u.position).to({
                x: o.position.x,
                y: o.position.y,
                z: o.position.z
            }, Math.random() * t + t).easing(Easing.Exponential.InOut).start();

            this.group.add(tw);

            let tw2 = new Tween(u.rotation).to({
                x: o.rotation.x,
                y: o.rotation.y,
                z: o.rotation.z
            }, Math.random() * t + t).easing(Easing.Exponential.InOut).start();

            this.group.add(tw2);
        }

        let tw3 = new Tween(this).to({}, t * 2).onUpdate(this.render.bind(this)).start();
        this.group.add(tw3);
    }

    setUserList(list: any) {
        this.userList = list;
    }

    addUser(item: any) {
        this.userList.push(item);
        this.initData(item, this.userList.length - 1);
    }

    addUsers(items: any[]) {
        for (let item in items) {
            this.addUser(item);
        }
    }

    init3D() {
        this.initUserList();
        this.autoTransform();
        this.animate();
    }
}